//--------------------------------------------------------------------------------
// This file is part of the Windows Workflow Foundation Sample Code
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// This source code is intended only as a supplement to Microsoft
// Development Tools and/or on-line documentation.  See these other
// materials for detailed information regarding Microsoft code samples.
// 
// THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//--------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Workflow.ComponentModel;
using System.Globalization;

namespace Microsoft.Samples.Workflow.UI.Design
{
    class TransitionsReferenceService : IReferenceService
    {
        private IReferenceService originalReference = null;
        private List<ReferenceHolder> references = null;
        private IServiceProvider serviceProvider = null;

        private TransitionsReferenceService() { }

        public TransitionsReferenceService(IReferenceService originalReference, IServiceProvider serviceProvider)
        {
            if (originalReference == null)
                throw new ArgumentNullException("originalReference");

            this.originalReference = originalReference;
            this.serviceProvider = serviceProvider;

            IComponentChangeService cs = this.serviceProvider.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
            if (cs != null)
                cs.ComponentChanged += new ComponentChangedEventHandler(this.OnComponentChanged);
        }

        IComponent IReferenceService.GetComponent(object reference)
        {
            IComponent component = this.originalReference.GetComponent(reference);
            if (component == null)
            {
                EnsureReferences();

                foreach (ReferenceHolder holder in this.references)
                {
                    if (object.ReferenceEquals(holder.Reference, reference))
                    {
                        component = holder.SitedComponent;
                        break;
                    }
                }
            }
            return component;
        }

        string IReferenceService.GetName(object reference)
        {
            string name = this.originalReference.GetName(reference);
            if (name == null)
            {
                EnsureReferences();

                foreach (ReferenceHolder holder in this.references)
                {
                    if (object.ReferenceEquals(holder.Reference, reference))
                    {
                        name = holder.Name;
                        break;
                    }
                }
            }
            return name;
        }

        object IReferenceService.GetReference(string name)
        {
            object reference = this.originalReference.GetReference(name);
            if (reference == null)
            {
                EnsureReferences();

                foreach (ReferenceHolder holder in this.references)
                {
                    if (string.Equals(holder.Name, name, StringComparison.OrdinalIgnoreCase))
                    {
                        reference = holder.Reference;
                        break;
                    }
                }
            }
            return reference;
        }

        object[] IReferenceService.GetReferences()
        {
            List<object> allReferences = new List<object>(this.originalReference.GetReferences());

            EnsureReferences();

            foreach (ReferenceHolder holder in this.references)
                allReferences.Add(holder.Reference);

            return allReferences.ToArray();
        }

        object[] IReferenceService.GetReferences(Type baseType)
        {
            List<object> allReferences = new List<object>(this.originalReference.GetReferences());

            EnsureReferences();

            foreach (ReferenceHolder holder in this.references)
            {
                if (baseType.IsAssignableFrom(holder.Reference.GetType()))
                    allReferences.Add(holder.Reference);
            }

            return allReferences.ToArray();
        }

        void EnsureReferences()
        {
            if (this.references == null)
            {
                IContainer container = this.serviceProvider.GetService(typeof(IContainer)) as IContainer;
                if (container == null)
                    throw new InvalidOperationException(SR.GetString(SR.General_MissingService, typeof(IContainer).FullName));

                this.references = new List<ReferenceHolder>();

                foreach (IComponent component in container.Components)
                {
                    if (component is Activity)
                    {
                        TransitionCollection transitions = ((Activity)component).GetValue(Navigator.TransitionsProperty) as TransitionCollection;
                        if (transitions != null)
                        {
                            foreach (Transition transition in transitions)
                                CreateReferences(transition);
                        }
                    }
                }
            }
        }

        void OnComponentChanged(object sender, ComponentChangedEventArgs cevent)
        {
            this.references = null;
        }

        void CreateReferences(Transition transition)
        {
            CreateReferences(transition.Target, transition, transition.Activity);
        }

        void CreateReferences(string trailingName, object reference, IComponent sitedComponent)
        {
            if (object.ReferenceEquals(reference, null))
                return;

            this.references.Add(new ReferenceHolder(trailingName, reference, sitedComponent));
            foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(reference, new Attribute[] { BrowsableAttribute.Yes }))
            {
                object value = null;
                try
                {
                    value = property.GetValue(reference);
                }
                catch
                {
                    // Work around!!! if an property getter throws exception then we ignore it.
                }
                if (value != null)
                {
                    BrowsableAttribute[] browsableAttrs = (BrowsableAttribute[])(value.GetType().GetCustomAttributes(typeof(BrowsableAttribute), true));
                    if (browsableAttrs.Length > 0 && browsableAttrs[0].Browsable)
                    {
                        CreateReferences(string.Format(CultureInfo.InvariantCulture, "{0}.{1}", new object[] { trailingName, property.Name }), property.GetValue(reference), sitedComponent);
                    }
                }
            }
        }

        class ReferenceHolder
        {
            string trailingName;
            object reference;
            IComponent sitedComponent;
            string fullName;

            internal ReferenceHolder(string trailingName, object reference, IComponent sitedComponent)
            {
                this.trailingName = trailingName;
                this.reference = reference;
                this.sitedComponent = sitedComponent;
            }

            internal string Name
            {
                get
                {
                    if (this.fullName == null)
                    {
                        if (this.sitedComponent != null && this.sitedComponent.Site != null && this.sitedComponent.Site.Name != null)
                            this.fullName = string.Format(CultureInfo.InvariantCulture, "{0}{1}", new object[] { this.sitedComponent.Site.Name, this.trailingName });
                        else
                           this.fullName = string.Empty;
                    }
                    return this.fullName;
                }
            }

            internal object Reference
            {
                get
                {
                    return this.reference;
                }
            }

            internal IComponent SitedComponent
            {
                get
                {
                    return this.sitedComponent;
                }
            }
        }
    }
}